""" 
this file contains the class to create the basic morphology of a CA3 neuron
according to Guy Major and Peter Jonas as defined in 'ca3_15.nrn'

jose.guzman@ist.ac.at

TODO:
1.- Implement 3D for rendering shape for Sage
2.- Validation via unittest
"""

from CA3 import __path__ as path

from numpy import loadtxt

def load_CA3_15geometry():
    """ 
    loads geometry from the  ca3_15.nrn file
    Returns a Numpy matrix with nsec x (nseg, L, diam ) 
    NOTE that this function strongly depends upon ca3_15.nrn
    """
    fp = path[0] + '/morphology/ca3_15.nrn'
    data = loadtxt(fp, skiprows = 201, dtype = object,\
        usecols = (1, 2, 3), comments = 'print')

    geometry = list()

    for line in data:
        # read nseg
        nseg = int(line[0][6:])
        # read L
        length = float(line[1][2:])
        # read diam
        end_index = line[2].index('}')
        diam = float(line[2][5:end_index])
        param = (nseg, length, diam) # tuple with values

        geometry.append(param)

    return geometry
    
def load_CA3_15dendconnectivity():
    """ 
    load the connection pattern of the dendrites 
    from the file ca3_15.nrn
    returns a matrix with nsec x (child - parent)
    NOTE that this function strongly depends upon ca3_15.nrn
    """
    fp = path[0] + '/morphology/ca3_15.nrn'
    data = loadtxt(fp, skiprows = 32, dtype = object,\
        usecols = (0, 2))

    # use only 160 connections
    connect = data[:160]

    dend_connect = list()
    for line in connect:
        # read parent
        string = line[0]
        start_index = string.index('[')
        parent = int(string[start_index+1:-1])
        # read child
        string = line[1]
        start_index = string.index('[')
        child = int(string[start_index+1:-5])
        param = (child, parent)
        
        dend_connect.append(param)

    return dend_connect
        
class CA3Cell(object):
    """
    this is a model of a CA3 pyramidal cell morphology from Guy Major 
    & Peter Jonas adapted to Python by Jose Guzman
    NOTE that this class strongly depends on file ca3_15.nrn

    """
    def __init__(self):
        from neuron import h, nrn

        # SOMA
        # note that Section will insert default cm and Ra
        self.soma = h.Section(name='soma', cell = self)

        # DEND
        # 0-73 basal dendrites, 74-166 apical dendrites
        self.ds = list()
        for i in range(167):
            if i<74:
                dend_name = 'basal_dendrite ' + str(i)
            else:
                dend_name = 'apical_dendrite ' + str(i)
    
            self.ds.append(h.Section(name = dend_name, cell = self))
        
        # ** TOPOLOGY
        # right (1) is basal, left (0) is apical areas
        self._morphinfo = str()
        
        # connect 4 principal basal dendrites to right end of the soma (1)
        basal_list = [0, 13, 32, 47]
        for dend_index in basal_list:
            self.ds[dend_index].connect(self.soma, 1, 0)
            self._morphinfo += "basal dendrite ds[%d] connected to right (1) soma\n"\
                    %dend_index

        # connect 3 principal apical dendrites to left end of the soma (0)
        apical_list = [74, 106, 119]
        for dend_index in apical_list:
            self.ds[dend_index].connect(self.soma, 0, 0)
            self._morphinfo +="apical dendrite ds[%d] connected to left (0) soma\n"\
                    %dend_index

        # connection within the dendritic processes themselves:
        
        # ** GEOMETRY
        self.soma.nseg = 3
        self.soma.L = self.soma.diam = 30.

        # geometry for the dendrites
        geometry = load_CA3_15geometry()
        for index, param in enumerate(geometry):
            self.ds[index].nseg, self.ds[index].L, self.ds[index].diam = param

        # ** CONNECTIVITY
        dend_connections = load_CA3_15dendconnectivity()
        for child, parent in dend_connections:
            self.ds[child].connect(self.ds[parent], 1, 0)

        # Iterators!!!
        # of the sections connected to the soma
        self.allsec = h.SectionList()
        self.allsec.wholetree(sec = self.soma)

        self.allbasalsec = iter( [self.ds[i] for i in range(74)])
        self.allapicalsec = iter( [self.ds[i] for i in range(75,167)])

        # informative attributes
        self._nseg, self._nsec = self._count_nsec_nseg()
        
    def _count_nsec_nseg(self):
        """ counts the number of segments in the cell """
        counter_nseg = counter_nsec = 0
        for sec in self.allsec:
            counter_nsec +=1                
            for seg in sec:
                counter_nseg +=1

        return (counter_nsec, counter_nseg)

    def _info(self):
        """ retrieves information about the morphology """
        print('CA3b morphology with')
        print('%d segments and %d sections'%(self._nseg, self._nsec))
        print(self._morphinfo) 

    def set_Vrest(self, Vrest):
        """
        sets the resting membrane potential of the cell
        and the leak reversal potential
        to the value given in Vrest
        """

        for sec in self.allsec:
            for seg in sec:
                seg.v = Vrest
                seg.e_pas = Vrest

    # only getters for nseg and nsec
    nseg = property(lambda self: self._nseg)
    nsec = property(lambda self: self._nsec)

    # only setter for Vrest
    Vrest = property(lambda self: self.set_Vrest)

    morphinfo = property(lambda self: self._info)
